import ubluetooth as bt
import struct
from utime import sleep_ms
import gc
from ble.const import BLEConst
from ble.tools import BLETools

PACK = struct.pack

class GenericAccess(object):
	UUID = bt.UUID(0x1800)

	__DEVICE_NAME_CHAR = (bt.UUID(0x2A00), bt.FLAG_READ,)
	__APPEARANCE_CHAR = (bt.UUID(0x2A01), bt.FLAG_READ,)
	__PERIPHERAL_PREFERRED_CONNECTION_PARAMETERS = (bt.UUID(0x2A04), bt.FLAG_READ,)
	__CENTRAL_ADDRESS_RESOLUTION = (bt.UUID(0x2AA6), bt.FLAG_READ,)

	SERVICE = (
		UUID,
		(
			__DEVICE_NAME_CHAR,
			__APPEARANCE_CHAR,
			__PERIPHERAL_PREFERRED_CONNECTION_PARAMETERS,
			__CENTRAL_ADDRESS_RESOLUTION
		)
	)

	class Values(object):
		DEVICE_NAME = PACK("<14s", "Walkline KPB20".encode())
		APPEARANCE = PACK("<h", 961)
		PERIPHERAL_PREFERRED_CONNECTION_PARAMETERS = PACK("<4h", 40, 80, 10, 300) # (6, 6, 60, 300) <- ble mouse # (min, max, latency, timeout), for min & max, 1 = 1.25ms
		CENTRAL_ADDRESS_RESOLUTION = PACK("<b", 1)


class GenericAttribute(object):
	UUID = bt.UUID(0x1801)

	# __CLIENT_CHARACTERISTIC_CONFIGURATION_DESC = (bt.UUID(0x2902), bt.FLAG_READ | bt.FLAG_WRITE)

	# __GENERIC_ATTRIBUTE_DESCS = (__CLIENT_CHARACTERISTIC_CONFIGURATION_DESC,)

	__SERVICE_CHANGED_CHAR = (bt.UUID(0x2A05), bt.FLAG_NOTIFY,) # __GENERIC_ATTRIBUTE_DESCS)

	SERVICE = (
		UUID,
		(
			__SERVICE_CHANGED_CHAR,
		),
	)

	class Values(object):
		pass


class BatteryService(object):
	UUID = bt.UUID(0x180F)
	
	# __CLIENT_CHARACTERISTIC_CONFIGURATION_DESC = (bt.UUID(0x2902), bt.FLAG_READ | bt.FLAG_WRITE)
	# __CHARACTERISTIC_PRESENTATION_FORMAT_DESC = (bt.UUID(0x2904), bt.FLAG_READ)

	# __BATTERY_LEVEL_DESCS = (__CLIENT_CHARACTERISTIC_CONFIGURATION_DESC,)

	__BATTERY_LEVEL_CHAR = (bt.UUID(0x2A19), bt.FLAG_READ | bt.FLAG_NOTIFY,) # __BATTERY_LEVEL_DESCS)
	
	SERVICE = (
		UUID,
		(
			__BATTERY_LEVEL_CHAR,
		),
	)

	class Values(object):
		BATTERY_LEVEL_INT = PACK("<B", int(100))


class DeviceInformation(object):
	UUID = bt.UUID(0x180A)

	__MODEL_NUMBER_STRING_CHAR = (bt.UUID(0x2A24), bt.FLAG_READ,)
	__SERIAL_NUMBER_STRING_CHAR = (bt.UUID(0x2A25), bt.FLAG_READ,)
	__FIRMWARE_REVISION_STRING_CHAR = (bt.UUID(0x2A26), bt.FLAG_READ,)
	__HARDWARE_REVISION_STRING_CHAR = (bt.UUID(0x2A27), bt.FLAG_READ,)
	__SOFTWARE_REVISION_STRING_CHAR = (bt.UUID(0x2A28), bt.FLAG_READ,)
	__MANUFACTURER_NAME_STRING_CHAR = (bt.UUID(0x2A29), bt.FLAG_READ,)
	__PNP_ID_CHAR = (bt.UUID(0x2A50), bt.FLAG_READ,)

	SERVICE = (
		UUID,
		(
			__MODEL_NUMBER_STRING_CHAR,
			__SERIAL_NUMBER_STRING_CHAR,
			__FIRMWARE_REVISION_STRING_CHAR,
			__HARDWARE_REVISION_STRING_CHAR,
			__SOFTWARE_REVISION_STRING_CHAR,
			__MANUFACTURER_NAME_STRING_CHAR,
			__PNP_ID_CHAR,
		),
	)

	class Values(object):
		MANUFACTURER_NAME_STRING = PACK("<17s", "Walkline Hardware".encode())
		MODEL_NUMBER_STRING = PACK("<11s", "WKHW BK-C87".encode())
		SERIAL_NUMBER_STRING = PACK("<36s", "4e897424-061d-4dd9-a798-454f79b37245".encode())
		FIRMWARE_REVISION_STRING = PACK("<4s", "v0.1".encode())
		HARDWARE_REVISION_STRING = PACK("<4s", "v0.1".encode())
		SOFTWARE_REVISION_STRING = PACK("<4s", "v0.1".encode())

		# key="1" value="Bluetooth SIG assigned Company Identifier value from the Assigned Numbers document"
		# key="2" value="USB Implementer’s Forum assigned Vendor ID value"
		# VendorID: https://www.bluetooth.com/specifications/assigned-numbers/company-identifiers/
		PNP_ID = PACK("<BHHH", 1, 0x0006, 0x01, 0x01) # 0x02E5: Espressif, 0x0006: Microsoft


class HumanInterfaceDevice(object):
	UUID = bt.UUID(0x1812)

	# __CLIENT_CHARACTERISTIC_CONFIGURATION_DESC = (bt.UUID(0x2902), bt.FLAG_READ | bt.FLAG_WRITE)
	__REPORT_REFERENCE_DESC = (bt.UUID(0x2908), bt.FLAG_READ)
	__REPORT_DESCRIPTOR = (__REPORT_REFERENCE_DESC,) # __CLIENT_CHARACTERISTIC_CONFIGURATION_DESC,)

	__PROTOCOL_MODE_CHAR = (bt.UUID(0x2A4E), bt.FLAG_READ | bt.FLAG_WRITE,)
	__REPORT_CHAR = (bt.UUID(0x2A4D), bt.FLAG_READ | bt.FLAG_WRITE | bt.FLAG_NOTIFY, __REPORT_DESCRIPTOR)
	__REPORT_MAP_CHAR = (bt.UUID(0x2A4B), bt.FLAG_READ,)
	__BOOT_KEYBOARD_INPUT_REPORT_CHAR = (bt.UUID(0x2A22), bt.FLAG_READ | bt.FLAG_WRITE | bt.FLAG_NOTIFY,)
	__BOOT_KEYBOARD_OUTPUT_REPORT_CHAR = (bt.UUID(0x2A32), bt.FLAG_READ | bt.FLAG_WRITE,)
	__HID_INFORMATION_CHAR = (bt.UUID(0x2A4A), bt.FLAG_READ,)
	__HID_CONTROL_POINT_CHAR = (bt.UUID(0x2A4C), bt.FLAG_WRITE,)
	
	SERVICE = (
		UUID,
		(
			__PROTOCOL_MODE_CHAR,
			__REPORT_CHAR,
			__REPORT_MAP_CHAR,
			__BOOT_KEYBOARD_INPUT_REPORT_CHAR,
			__BOOT_KEYBOARD_OUTPUT_REPORT_CHAR,
			__HID_INFORMATION_CHAR,
			__HID_CONTROL_POINT_CHAR,
		),
	)

	class Values(object):
		# key="0" value="Boot Protocol Mode"
		# key="1" value="Report Protocol Mode"
		PROTOCOL_MODE = PACK("<B", int(1))
		HID_INFORMATION = PACK("<Hbb", 0x0100, 0x00, 0b0011)
		# param 1: Report ID
		# param 2: Report Type
		# value="Input Report" key="1"
		# value="Output report" key="2"
		# value="Feature Report" key="3"
		REPORT_REFERANCE = PACK("<BB", int(1), int(1))

		# http://www.freebsddiary.org/APC/usb_hid_usages
		REPORT_MAP_DATA = [
			0x05, 0x01, # USAGE_PAGE (Generic Desktop)
			0x09, 0x06, # USAGE (Keyboard)
			0xA1, 0x01, # COLLECTION (Application)
			# 8 keycode
			0x05, 0x07, # 	USAGE_PAGE (Keyboard)
			0x19, 0xE0, # 	USAGE_MINIMUM (Keyboard LeftControl)
			0x29, 0xE7, # 	USAGE_MAXIMUM (Keyboard Right GUI)
			# keycode value 0 and 1
			0x15, 0x00, # 	LOGICAL_MINIMUM (0)
			0x25, 0x01, # 	LOGICAL_MAXIMUM (1)
			# Modifier byte
			0x75, 0x01, # 	REPORT_SIZE (1)
			0x95, 0x08, # 	REPORT_COUNT (8)
			0x81, 0x02, # 	INPUT (Data,Var,Abs)
			# Reserved byte
			0x95, 0x01, # 	REPORT_COUNT (1)
			0x75, 0x08, # 	REPORT_SIZE (8)
			0x81, 0x01, # 	INPUT (Cnst,Var,Abs)
			# # LED report
			0x95, 0x05, # 	REPORT_COUNT (5)
			0x75, 0x01, # 	REPORT_SIZE (1)
			0x05, 0x08, # 	USAGE_PAGE (LEDs)
			0x19, 0x01, # 	USAGE_MINIMUM (Num Lock)
			0x29, 0x05, # 	USAGE_MAXIMUM (Kana)
			0x91, 0x02, # 	OUTPUT (Data,Var,Abs)
			# # LED report padding
			0x95, 0x01, # 	REPORT_COUNT (1)
			0x75, 0x03, # 	REPORT_SIZE (3)
			0x91, 0x03, # 	OUTPUT (Cnst,Var,Abs)
			# Key arrays (6 bytes)
			0x95, 0x06, # 	REPORT_COUNT (6)
			0x75, 0x08, # 	REPORT_SIZE (8)
			0x15, 0x00, # 	LOGICAL_MINIMUM (0)
			0x25, 0x65, # 	LOGICAL_MAXIMUM (101)
			0x05, 0x07, # 	USAGE_PAGE (Keyboard)
			0x19, 0x00, # 	USAGE_MINIMUM (Reserved (no event indicated))
			0x29, 0x65, # 	USAGE_MAXIMUM (Keyboard Application)
			0x81, 0x00, # 	INPUT (Data,Ary,Abs)
			0xC0,		# END_COLLECTION
		]

class BLE_HID(object):
	def __init__(self, ble, name='wk_kb'):
		self.__ble = ble
		self.__conn_handle = None
		self.__last_addr = None
		self.__write = self.__ble.gatts_write
		self.__read = self.__ble.gatts_read
		self.__notify = self.__ble.gatts_notify

		self.__services = (GenericAccess.SERVICE, GenericAttribute.SERVICE, BatteryService.SERVICE, DeviceInformation.SERVICE, HumanInterfaceDevice.SERVICE)

		self.__ble.active(False)
		print("activating ble...")
		self.__ble.active(True)
		print("ble activated")

		self.__ble.config(rxbuf=256)
		self.__ble.config(gap_name="Walkline KPB20")
		# self.__ble.config(mac="246f289da322") #bytes([0x24, 0x6f, 0x28, 0x9d, 0xa3, 0x22]))
		# print("mac: [{}]".format(BLETools.decode_mac(self.__ble.config("mac"))))
		print("rxbuf: [{}]".format(self.__ble.config("rxbuf")))

		self.__ble.irq(self.__irq)
		self.__register_services()

		self.__adv_payload = BLETools.advertising_hid_payload(
			services=[BatteryService.UUID, DeviceInformation.UUID, HumanInterfaceDevice.UUID],
			appearance=961 # https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Characteristics/org.bluetooth.characteristic.gap.appearance.xml
		)
		self.__resp_payload = BLETools.advertising_resp_payload(
			name="Walkline KPB20",
			services=[GenericAccess.UUID, GenericAttribute.UUID]
		)

		self.__advertise()

	def __advertise(self, interval_us=100000):
		self.__ble.gap_advertise(None)
		print("advertising...")
		self.__ble.gap_advertise(interval_us, adv_data=self.__adv_payload, resp_data=self.__resp_payload)

	def __register_services(self):
		(
			(
				self.__device_name_handle,
				self.__appearance_handle,
				self.__peripheral_preferred_connection_parameters_handle,
				self.__central_address_resolution_handle,
			),
			(
				self.__service_changed_handle,
				# self.__service_changed_ccc_descriptor_handle,
			),
			(
				self.__battery_level_handle,
				# self.__battery_level_ccc_descriptor_handle,
			),
			(
				self.__model_handle,
				self.__serial_handle,
				self.__firmware_handle,
				self.__hardware_handle,
				self.__software_handle,
				self.__manufacturer_handle,
				self.__pnp_id_handle,
			),
			(
				self.__protocol_mode_handle,
				self.__report_handle,
				self.__report_report_reference_descriptor_handle,
				self.__report_map_handle,
				self.__boot_kb_input_report_handle,
				self.__boot_kb_output_report_handle,
				self.__hid_information_handle,
				self.__hid_control_point_handle,
				# self.__boot_kb_input_report_descriptor_handle,
			),
		) = self.__ble.gatts_register_services(self.__services)
		
		print("services registed")

		self.__setup_generic_access()
		# self.__setup_generic_attribute()
		# self.set_battery_level()
		self.__setup_device_info()
		self.__setup_hid()
		print("characteristic values setup")

		# gc.collect()

	def __irq(self, event, data):
		if event == BLEConst.IRQ.IRQ_CENTRAL_CONNECT:
			self.__conn_handle, _, addr = data
			print("[{}] connected, handle: {}".format(BLETools.decode_mac(addr), self.__conn_handle))

			self.__ble.gap_advertise(None)

			sleep_ms(300)
			print("start bonding...")
			# try:
			self.__ble.gap_pair(self.__conn_handle, bond=True, mitm=True, lesc=True)
			# except OSError as ose:
			# 	if str(ose) == "[Errno 2] ENOENT":
			# 		self.__ble.gap_pair(self.__conn_handle)

			# self.__notify(self.__conn_handle, self.__battery_level_handle)
			# gc.collect()
		elif event == BLEConst.IRQ.IRQ_CENTRAL_DISCONNECT:
			self.__conn_handle, _, addr = data
			print("[{}] disconnected, handle: {}".format(BLETools.decode_mac(addr), self.__conn_handle))
			self.__conn_handle = None
			self.__advertise()
		elif event == BLEConst.IRQ.IRQ_MTU_EXCHANGED:
			print("MTU Exchange value: {}".format(data[1]))
			self.__ble.config(mtu=data[1])
		else:
			print("event: {}, data: {}".format(event, data))
		
		gc.collect()

	def __setup_generic_access(self):
		self.__write(self.__device_name_handle, GenericAccess.Values.DEVICE_NAME)
		self.__write(self.__appearance_handle, GenericAccess.Values.APPEARANCE)
		self.__write(self.__peripheral_preferred_connection_parameters_handle, GenericAccess.Values.PERIPHERAL_PREFERRED_CONNECTION_PARAMETERS)
		self.__write(self.__central_address_resolution_handle, GenericAccess.Values.CENTRAL_ADDRESS_RESOLUTION)

	def __setup_device_info(self):
		"""
		used for Device Information
		"""
		self.__write(self.__manufacturer_handle, DeviceInformation.Values.MANUFACTURER_NAME_STRING)
		self.__write(self.__model_handle, DeviceInformation.Values.MODEL_NUMBER_STRING)
		self.__write(self.__serial_handle, DeviceInformation.Values.SERIAL_NUMBER_STRING)
		self.__write(self.__firmware_handle, DeviceInformation.Values.FIRMWARE_REVISION_STRING)
		self.__write(self.__hardware_handle, DeviceInformation.Values.HARDWARE_REVISION_STRING)
		self.__write(self.__software_handle, DeviceInformation.Values.SOFTWARE_REVISION_STRING)
		self.__write(self.__pnp_id_handle, DeviceInformation.Values.PNP_ID)

	def __setup_hid(self):
		self.__write(self.__protocol_mode_handle, HumanInterfaceDevice.Values.PROTOCOL_MODE)
		self.__write(self.__report_report_reference_descriptor_handle, HumanInterfaceDevice.Values.REPORT_REFERANCE)
		self.__write(self.__hid_information_handle, HumanInterfaceDevice.Values.HID_INFORMATION)
		self.__write(self.__report_map_handle, bytes(HumanInterfaceDevice.Values.REPORT_MAP_DATA))

	def set_battery_level(self):
		self.__write(self.__battery_level_handle, BatteryService.Values.BATTERY_LEVEL_INT)
		self.__notify(self.__conn_handle, self.__battery_level_handle)
		# self.__write(self.__battery_level_ccc_descriptor_handle, __PACK("<H", 0x0100))

	def send_key(self, keycode):
		self.__write(self.__report_handle, bytes([0x0, 0x0, keycode, 0x0, 0x0, 0x0, 0x0, 0x0]))
		self.__notify(self.__conn_handle, self.__report_handle)
		self.__write(self.__report_handle, bytes([0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]))
		self.__notify(self.__conn_handle, self.__report_handle)

	def send_key_down(self, keycode):
		self.__write(self.__report_handle, bytes([0x0, 0x0, keycode, 0x0, 0x0, 0x0, 0x0, 0x0]))
		self.__notify(self.__conn_handle, self.__report_handle)

	def send_key_up(self):
		self.__write(self.__report_handle, bytes([0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]))
		self.__notify(self.__conn_handle, self.__report_handle)


hid = None
keypad = None
# keycode = 0

def init_button():
	global hid

	from machine import Pin
	import random

	def button_click_cb(timer):
		"""
		a:4 b:5 c:6 d:7 e:8f:9 g:10 h:11 i:12 j:13 k:14 l:15 m:16 n:17 o:18 p:19 q:20 r:21 s:22 t:23 u:24 v:25 w:26 x:27 y:28 z:29
		"""
		keycode = random.randint(4, 29)
		# keycode = random.randint(7, 54)

		print("send key: {}".format(keycode))

		# hid.send_key(keycode)
		hid.send_key_down(keycode)
		hid.send_key_up()

	button = Pin(0, Pin.IN, Pin.PULL_UP)
	button.irq(button_click_cb, Pin.IRQ_RISING)

	print("button initialized")

def main():
	global hid

	ble = bt.BLE()
	hid = BLE_HID(ble)
	init_button()


if __name__ == "__main__":
	try:
		main()
	except KeyboardInterrupt:
		print("\nPRESS CTRL+D TO RESET DEVICE")
